package dao

import (
	"context"
	"github.com/ecodeclub/ekit/sqlx"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

var ErrSMSNotFound = gorm.ErrRecordNotFound

type AsyncSmsDAO interface {
	Insert(ctx context.Context, s AsyncSms) error
	GetWaitingSMS(ctx context.Context) (AsyncSms, error)
	MarkSuccess(ctx context.Context, id int64) error
	MarkFailed(ctx context.Context, id int64) error
}

const (
	// 因为本身状态没有暴露出去，所以不需要在 domain 里面定义
	asyncStatusWaiting = iota
	// 失败了，并且超过了重试次数
	asyncStatusFailed
	asyncStatusSuccess
)

type AsyncGORMSmsDAO struct {
	db *gorm.DB
}

func (a *AsyncGORMSmsDAO) Insert(ctx context.Context, s AsyncSms) error {
	//插入一个异步记录,默认0就是asyncStatusWaiting状态，所以我们不需要设置这个值
	return a.db.WithContext(ctx).Create(s).Error
}

func (a *AsyncGORMSmsDAO) GetWaitingSMS(ctx context.Context) (AsyncSms, error) {
	//我们利用 select for update的方法来锁住记录
	//高并发情况下，这种方法对数据库压力大，但我们这里只会有一个gorouting来查询
	var s AsyncSms
	err := a.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		now := time.Now().UnixMilli()
		//为了避免偶发性的错误，我们这里只找一分钟前的
		endTime := now - time.Minute.Microseconds()

		er := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("utime < ? AND STATUS = ?", endTime, asyncStatusWaiting).
			First(&s).Error
		if er != nil {
			return er
		}

		er = tx.Where("id = ?", s.Id).
			Updates(map[string]any{
				"utime":     now,
				"retry_cnt": gorm.Expr("retry_cnt + 1"),
			}).Error
		if er != nil {
			return er
		}

		return nil
	})

	return s, err
}

func (a *AsyncGORMSmsDAO) MarkSuccess(ctx context.Context, id int64) error {
	now := time.Now().UnixMilli()
	return a.db.WithContext(ctx).Where("id = ?", id).Updates(map[string]any{
		"utime":  now,
		"status": asyncStatusSuccess,
	}).Error
}

func (a *AsyncGORMSmsDAO) MarkFailed(ctx context.Context, id int64) error {
	now := time.Now().UnixMilli()
	//只有到了最大重试次数我们才更新状态
	return a.db.WithContext(ctx).Where("id = ? AND `retry_cnt` >= 3", id).Updates(map[string]any{
		"utime":  now,
		"status": asyncStatusFailed,
	}).Error
}

type AsyncSms struct {
	Id int64 `gorm:"index"`

	Config   sqlx.JsonColumn[SmsConfig]
	RetryCnt int64
	//最大重试次数
	RetryMax int64

	Status uint8

	Ctime int64
	Utime int64 `gorm:"index"`
}

type SmsConfig struct {
	TpiId   string
	Args    []string
	Numbers []string
}
